package com.support.config.security;

import org.slf4j.MDC;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.logout.HeaderWriterLogoutHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.header.writers.ClearSiteDataHeaderWriter;

/**
 * Spring Security 简单的授权认证配置；<br>
 * 中文文档：https://springcloud.cc/spring-security-zhcn.html <br>
 * 官方文档：https://docs.spring.io/spring-security/site/docs/5.1.1.RELEASE/reference/htmlsingle/
 *
 * <pre>
 *     实现以下方法指定登录用户及权限
 *     \@Bean
 *     public UserDetailsService userDetailsService() {
 *         final InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
 *         manager.createUser(User.builder().username("admin").password(passwordEncoder().encode("superadmin")).roles("USER", "ADMIN").build());
 *         manager.createUser(User.builder().username("user").password(passwordEncoder().encode("111111")).roles("USER").build());
 *         return manager;
 *     }
 *
 *     登录url:/login
 *     支持三种登录传参
 *
 *     ### 登录： JSON 模式
 *         POST http://{{host}}:{{port}}/login
 *          Content-Type: application/json
 *
 *          {"username": "admin","password": "superadmin"}
 *     ###
 *     ### 登录：表单模式
 *         POST http://{{host}}:{{port}}/login
 *          Content-Type: application/x-www-form-urlencoded
 *
 *          username=admin&password=superadmin
 *     ###
 *     ### Basic 模式，无 session
 *         GET http://{{host}}:{{port}}/user
 *         Content-Type: application/json
 *         Authorization: basic YWRtaW46c3VwZXJhZG1pbg==
 *     ###
 *
 *
 * @author 谢长春 2018/12/4
 */
public abstract class SimpleAuthAdapter extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        final AuthHandler authHandler = new AuthHandler();
        http
//                .cors().and()
                .csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and())
                // http 响应头追加链路追踪
                .headers(headers -> headers
                                .addHeaderWriter((req, res) -> res.addHeader("X-Trace-Id", MDC.get("traceId")))
                                .xssProtection()
                        // .and().cacheControl() // 启用缓存
                )
                // 用户访问未经授权的rest API，返回错误码401（未经授权）
                .exceptionHandling().authenticationEntryPoint(authHandler).accessDeniedHandler(authHandler)
//                    // 指定会话策略；ALWAYS:总是创建HttpSession, IF_REQUIRED:只会在需要时创建一个HttpSession, NEVER:不会创建HttpSession，但如果它已经存在，将可以使用HttpSession, STATELESS:永远不会创建HttpSession，它不会使用HttpSession来获取SecurityContext
//                    .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 基于Token，不需要Session
                .and().authorizeRequests()
                // TODO 建议在类头部或方法头上加权限注解，这里配置太多的权限容易混淆
                .antMatchers("/").permitAll()
                .antMatchers("/error").permitAll() // HomeController 中需要异常处理方法
                // 所有方法都需要登录认证
                .anyRequest().authenticated()
//                    .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                // 开启 Basic 认证
                .and().httpBasic()
                .and().formLogin()
                .successHandler(authHandler)
                .failureHandler(authHandler)
                // 配置退出参数
                .and().logout()
                .logoutSuccessHandler(authHandler)
                .invalidateHttpSession(true) // 指定是否在注销时让HttpSession无效, 默认设置为 true
                .deleteCookies("JSESSIONID") // 删除 JSESSIONID
                .addLogoutHandler(new SecurityContextLogoutHandler()) // 添加一个LogoutHandler, // 清除 session
                // 向客户端发送 清除 “cookie、storage、缓存” 消息
                .addLogoutHandler(new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(ClearSiteDataHeaderWriter.Directive.ALL)))
        ;
        { // 自定义过滤器
//            http.addFilterBefore(
//                    new JsonUsernamePasswordAuthenticationFilter(authenticationManager(), authHandler, authHandler),
//                    UsernamePasswordAuthenticationFilter.class
//            );
            // 链路追踪过滤器注册到 Spring Security 过滤器前面； ChannelProcessingFilter.class, SecurityContextPersistenceFilter.class
//            http.addFilterBefore(new RequestIdFilter(), ChannelProcessingFilter.class);
        }
    }

    @ConditionalOnMissingBean(PasswordEncoder.class)
    @Bean
    public PasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}
